#include "widget.h"
#include "ui_widget.h"
#include <QVBoxLayout>
#include <QMessageBox>
#include <QTcpSocket>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建垂直布局管理器
    QVBoxLayout*layout=new QVBoxLayout(this);
    //创建QListWidget对象
    list_widget=new QListWidget(this);
    list_widget->setGeometry(50,50,700,500);
    //添加
    layout->addWidget(list_widget);
    //显示
    this->setLayout(layout);

    //1、修改窗口标题
    this->setWindowTitle("服务器");
    //2、创建QTcpServer对象
    tcpserver=new QTcpServer(this);
    //3、通过信号槽机制，指定连接到来时如何处理
    connect(tcpserver,&QTcpServer::newConnection,this,&Widget::processConnection);
    //当有新连接到来时，就会触发newConnection信号，就会执行对应的槽函数
    //4、绑定并监听端口号，这个操作需要最后做
    bool ret=tcpserver->listen(QHostAddress::Any,9090);
    if(ret==false)
    {
        //绑定+监听失败
        QMessageBox::critical(this,"服务器启动失败",tcpserver->errorString());
        return;
    }
}

Widget::~Widget()
{
    delete ui;
}
//连接到来时的处理函数
void Widget::processConnection()
{
    //1、通过tcpserver拿到socket对象，通过这个对象来进行此客户端和服务器的通信
    QTcpSocket *clientsocket=tcpserver->nextPendingConnection();
    //2、构建日志，将日志显示到listWidget中
    //clientsocket->peerAddress()：对端ip地址
    //clientsocket->peerPort()：对端port
    QString log="["+clientsocket->peerAddress().toString()+":"+QString::number(clientsocket->peerPort())+"] 客户端上线";
    //设置到list_widget中
    list_widget->addItem(log);

    //3、当消息到来时，设置槽函数
    connect(clientsocket,&QTcpSocket::readyRead,this,[=](){
       //1、读取请求报文 此处readAll()的返回值为QByteArray，通过赋值转换成QString对象
        QString request=clientsocket->readAll();
        //2、根据请求去拿到响应
        const QString response=process(request);
        /*
         * 这里应该处理一下粘包问题
         * 因为TCP是面向字节流的，一个完整的TCP请求，可能会分成多段字节数组进行传输
         * 虽然TCP已经帮我们处理了很多问题，但是TCP本身不负责区分，从哪里到哪里是一个完整的应用层数据包(粘包问题)
         * 解决粘包问题具体思路：
         * 每次收到数据后都放到一个大的字节数组缓冲区中，并且提前约定好应用层协议的格式(利用分隔符、长度等方法来区分是不是一个完整的TCP报文)
         * 再按照协议格式对缓冲区进行更加细致的解析处理即可
         */
        //3、将响应写回到客户端
        clientsocket->write(response.toUtf8());
        //4、将上述信息记录到list_widget日志中
        QString log="["+clientsocket->peerAddress().toString()+":"+QString::number(clientsocket->peerPort())+"]"
                +"request: "+request+", response: "+response;
        list_widget->addItem(log);
    });
    //当消息到来时，会自动触发readyRead信号，就会去执行对应的槽函数
    //这里完美的借助了信号槽机制
    //即便是多个请求，槽函数也是可以顺利执行到的

    //4、客户端断开连接时，通过信号槽绑定处理方法
    /* TCP socket中，每个客户端都有一个QTcpSocket对象用来和服务端进行通信，
     * 随着服务端的运行,客户端越来越多，如果不进行释放clientsocket连接对象，那么程序就会越来越卡
     * QTcpServer QUdpSocket只有一份，不释放也影响不大。
     * 如果不释放QTcpSocket对象，就会造成内存泄漏和文件描述符泄露
     * 对于这两个问题来说，文件描述符泄露危害更大，文件描述符表的长度是操作系统的一个参数
     * Linux通过ulimits命令来查看和调整
     * 文件描述符更紧张，更容易泄露完
    */
    connect(clientsocket,&QTcpSocket::disconnected,this,[=](){
        //1、把断开连接的信息通过日志显示出来
        QString log="["+clientsocket->peerAddress().toString()+":"+QString::number(clientsocket->peerPort())+"] 客户端下线";
        //显示给日志
        list_widget->addItem(log);
        //2、手动释放clientsocket对象，使用delete直接释放不太好，用deleteLater更加合适
        //delete clientsocket;
        /* 当前这个槽函数，就是围绕着连接对象clientSocket来进行操作的
         * 一旦此时delete就意味着其他逻辑无法使用clientsocket对象
         * 要保证delete是这个槽函数中的最后一步，而且delete操作要能执行到，不会被return/抛异常等操作给跳过
         * clientsocket->deleteLater()操作不是立即销毁clientsocket，而是告诉Qt，下一轮事件循环中，再进行上述的销毁操作
         * 槽函数都是再事件循环中进行的，进入到下一轮事件循环，就意味着上一轮事件循环肯定结束了，也就意味着当前的槽函数肯定执行结束了
         * 上述做法，对比java/python等全自动化的垃圾回收机制更好用一些。
         */
        clientsocket->deleteLater();
    });
}
//业务处理函数
QString Widget::process(const QString request)
{
    return request;
}

